#include "postgres.h"
#include "storage/fd.h"
#include "storage/bufpage.h"
#include "storage/page_common.h"
#include "storage/page_compression.h"
#include "utils/rel.h"
#include "utils/wait_event.h"
#include "common/pg_lzcompress.h"

#ifdef HAVE_LIBZ
#include <zlib.h>
#endif

#ifdef USE_ZSTD
#include <zstd.h>
#endif

#define MIN_FALLOCATE_SIZE (4096)

const KPHC_CFS_STORAGE_TYPE COMMON_STORAGE = 0;

uint32 AddrChecksum32(const KPHCCfsExtentAddress *cfsExtentAddress, const int needChunks);

const uint32 INDEX_OF_HALF_BLCKSZ = 0;
const uint32 INDEX_OF_QUARTER_BLCKSZ = 1;
const uint32 INDEX_OF_EIGHTH_BLCKSZ = 2;
const uint32 INDEX_OF_SIXTEENTHS_BLCKSZ = 3;

const int KPHC_EXTENT_OPEN_FILE = 0;
const int KPHC_WRITE_BACK_OPEN_FILE = 1;
const int KPHC_EXTENT_CREATE_FILE = 2;

const KPHCCmpBitStuct gCmpBitStruct[] = {{KPHC_CMP_BYTE_CONVERT_LEN, 0x01, 15},
    {KPHC_CMP_DIFF_CONVERT_LEN, 0x01, 14},
    {KPHC_CMP_PRE_CHUNK_LEN, 0x07, 11},
    {KPHC_CMP_LEVEL_SYMBOL_LEN, 0x01, 10},
    {KPHC_CMP_LEVEL_LEN, 0x1F, 5},
    {KPHC_CMP_ALGORITHM_LEN, 0x07, 2},
    {KPHC_CMP_CHUNK_SIZE_LEN, 0x03, 0}};

const uint32 CHUNK_SIZE_LIST[4] = {BLCKSZ / 2, BLCKSZ / 4, BLCKSZ / 8, BLCKSZ / 16};

uint8 ConvertChunkSize(uint32 compressedChunkSize, bool *success)
{
    uint8 chunkSize = INDEX_OF_HALF_BLCKSZ;
    switch (compressedChunkSize) {
        case BLCKSZ / 2:
            chunkSize = INDEX_OF_HALF_BLCKSZ;
            break;
        case BLCKSZ / 4:
            chunkSize = INDEX_OF_QUARTER_BLCKSZ;
            break;
        case BLCKSZ / 8:
            chunkSize = INDEX_OF_EIGHTH_BLCKSZ;
            break;
        case BLCKSZ / 16:
            chunkSize = INDEX_OF_SIXTEENTHS_BLCKSZ;
            break;
        default:
            *success = false;
            return chunkSize;
    }
    *success = true;
    return chunkSize;
}

size_t CompressReservedLen(const char* page)
{
    size_t length = offsetof(KPHCHeapPageCompressData, page_header) - offsetof(KPHCHeapPageCompressData, data);
    return GetPageHeaderSize(page) + length; // 宏不完整
}

int CompressPageBufferBound(const char* page, uint8 algorithm)
{
    switch (algorithm) {
        case COMPRESS_ALGORITHM_PGLZ:
            return BLCKSZ * 2;
        case COMPRESS_ALGORITHM_ZSTD:
#ifdef USE_ZSTD
            return ZSTD_compressBound(BLCKSZ - CompressReservedLen(page));
#else
            return -1;
#endif
        case COMPRESS_ALGORITHM_PGZSTD:
            return BLCKSZ + 4;
        case COMPRESS_ALGORITHM_ZLIB:
#ifdef HAVE_LIBZ
            return compressBound(BLCKSZ - CompressReservedLen(page));
#else
            return -1;
#endif
        default:
            return -1;
    }
}

void SetupPageCompressForRelation(RelFileNode *node, PageCompressOpts *compressOptions, const char *relationName)
{
    uint32 algorithm = (uint32)compressOptions->compressType;
    bool symbol;
    bool success;
    uint8 chunkSize;
    uint8 preallocChunks;

    if (algorithm == (uint32)COMPRESS_TYPE_NONE) {
        node->opt = 0;
    } else {
        uint8 compressLevel;
        symbol = false;
        if (compressOptions->compressLevel >= 0) {
            symbol = true;
            compressLevel = (uint8)compressOptions->compressLevel;
        } else {
            symbol = false;
            compressLevel = (uint8)-compressOptions->compressLevel;       
        }

        success = false;
        chunkSize = ConvertChunkSize(compressOptions->compressChunkSize, &success);

        preallocChunks = 0;
        if (compressOptions->compressPreallocChunks >= BLCKSZ / compressOptions->compressChunkSize) {
            ;
        } else {
            preallocChunks = (uint8)compressOptions->compressPreallocChunks;
        }
        node->opt = 0;
        SET_COMPRESS_OPTION((*node), (int)compressOptions->compressByteConvert,
                            (int)compressOptions->compressDiffConvert,
                            preallocChunks, (int)symbol, compressLevel, algorithm, chunkSize);
    }
}

size_t SizeOfExtentAddress(uint16 chunkSize)
{
    if (chunkSize == 0) {
        return -1;
    }
    return offsetof(KPHCCfsExtentAddress, chunknos) + sizeof(uint16) * BLCKSZ / chunkSize;
}

off_t OffsetOfPageCompressChunk(uint16 chunkSize, int chunkNo)
{
    return chunkSize * (chunkNo - 1);
}

size_t SizeOfExtentAddressByChunks(uint8 nChunks)
{
    return offsetof(KPHCCfsExtentAddress, chunknos) + sizeof(uint16) * nChunks;
}

// 获取extent地址
KPHCCfsExtentAddress *KPHCGetExtentAddress(KPHCCfsExtentHeader *header, uint16 blockOffset)
{
    uint16 chunkSize = header->chunk_size;
    size_t headerOffset = offsetof(KPHCCfsExtentHeader, cfsExtentAddress);
    size_t sizeOfExtentAddress = SizeOfExtentAddress(chunkSize);
    return (KPHCCfsExtentAddress *)(((char *)header) + headerOffset + blockOffset * sizeOfExtentAddress);
}

uint32 AddrChecksum32(const KPHCCfsExtentAddress *cfsExtentAddress, const int needChunks)
{
    size_t i;
    const size_t uintLen = sizeof(uint32);
    uint32 checkSum = 0;
    char *addr = ((char *) cfsExtentAddress) + sizeof(uint32);
    size_t len = SizeOfExtentAddressByChunks((uint8)needChunks) - sizeof(uint32);
    do {
        if (len >= uintLen) {
            checkSum += *((uint32*)(void*)addr);
            addr += uintLen;
            len -= uintLen;
        } else {
            char *finalNum = (char*)malloc(uintLen * sizeof(char));
            memset(finalNum, 0, uintLen * sizeof(char));
            i = 0;
            for(; i < len; ++i) {
                finalNum[i] = addr[i];
            }
            checkSum += *((uint32 *)finalNum);
            len -= i;
        }
    } while (len);
    return checkSum;
}

static inline bool ExtendChunksOfBlockCore(KPHCCfsExtentHeader *cfsExtentHeader, KPHCCfsExtentAddress *cfsExtentAddress,
                                            uint8 needChunks, uint8 actualUse)
{
    bool res;
    uint8 allocateNumber;
    uint32 chunkno;
    uint32 cksm;

    res = false;
    if (cfsExtentAddress->allocated_chunks < needChunks) {
        cfsExtentHeader->recycleInOrder = 0;
        allocateNumber = needChunks - cfsExtentAddress->allocated_chunks;
        chunkno = (pc_chunk_number_t)pg_atomic_fetch_add_u32(&cfsExtentHeader->allocated_chunks, (uint32)allocateNumber) + 1;
        for (int i = cfsExtentAddress->allocated_chunks; i < needChunks; ++i, ++chunkno) {
            cfsExtentAddress->chunknos[i] = (uint16)chunkno;
        }
        cfsExtentAddress->allocated_chunks = needChunks;
        res = true;
    }
    if (cfsExtentAddress->nchunks != actualUse) {
        cfsExtentAddress->nchunks = actualUse;
        res = true;
    }
    cksm = AddrChecksum32(cfsExtentAddress, cfsExtentAddress->allocated_chunks);
    if (cfsExtentAddress->checksum != cksm) {
        cfsExtentAddress->checksum = cksm;
        res = true;
    }
    return res;
}

static inline bool ExtendChunksOfBlock(KPHCCfsExtentHeader *cfsExtentHeader, KPHCExtentLocation *location, 
                                            uint8 needChunks, uint8 actualUse)
{
    KPHCCfsExtentAddress *cfsExtentAddress = KPHCGetExtentAddress(cfsExtentHeader, (uint16)location->extentOffset);
    return ExtendChunksOfBlockCore(cfsExtentHeader, cfsExtentAddress, needChunks, actualUse);
}

int KPHCCompressPage(const char* src, char* dst, int dst_size, const KPHCRelFileCompressOption option)
{
    int compressed_size;
#ifdef USE_ZSTD
    int8 level = option.compressLevelSymbol ? option.compressLevel : -option.compressLevel;
#endif
    size_t sizeOfHeaderData = SizeOfPageHeaderData;
    bool real_ByteConvert = false;
    bool success;
    int ret;
    KPHCPageCompressData* pcdptr;
    char* data = ((KPHCPageCompressData*)dst)->data;
    PageHeaderData *page = (PageHeaderData *)src;
    bool heapPageData = page->pd_special == BLCKSZ;
    switch (option.compressAlgorithm) {
        case COMPRESS_ALGORITHM_PGLZ: {
            success = KPHCLzCompress(src + sizeOfHeaderData, BLCKSZ - sizeOfHeaderData, (KPHC_PGLZ_Header *)data, heapPageData ? PGLZ_strategy_default : PGLZ_strategy_always);
            compressed_size = success ? VARSIZE(data) : BLCKSZ;
            compressed_size = compressed_size < BLCKSZ ? compressed_size : BLCKSZ;
            break;
        }
        case COMPRESS_ALGORITHM_ZSTD: {
#ifdef USE_ZSTD
            if (level == 0 || level < MIN_ZSTD_COMPRESSION_LEVEL || level > MAX_ZSTD_COMPRESSION_LEVEL) {
                level = DEFAULT_ZSTD_COMPRESSION_LEVEL;
            }
#ifndef FRONTEND
            compressed_size = ZSTD_compress(data, dst_size, src + sizeOfHeaderData, BLCKSZ - sizeOfHeaderData, level);
#else
            if (real_ByteConvert) {
                compressed_size =
                    ZSTD_compress(data, dst_size, src_copy + sizeOfHeaderData, BLCKSZ - sizeOfHeaderData, level);
            } else {
                compressed_size =
                    ZSTD_compress(data, dst_size, src + sizeOfHeaderData, BLCKSZ - sizeOfHeaderData, level);
            }
#endif
            if (ZSTD_isError(compressed_size)) {
                return -1;
            }
            break;
#else
                 return -1;
#endif
        }
        case COMPRESS_ALGORITHM_ZLIB: {
#ifdef HAVE_LIBZ
            int level = option.compressLevel;
            if (level == 0 || level < MIN_ZLIB_COMPRESSION_LEVEL || level > MAX_ZLIB_COMPRESSION_LEVEL) {
                level = DEFAULT_ZLIB_COMPRESSION_LEVEL;
            }
            ret = compress2((Bytef *)data, (uLongf *)&dst_size, (const Bytef *)(src + sizeOfHeaderData),
                BLCKSZ - sizeOfHeaderData, level);
            if (ret != Z_OK) {
                return -1;
            }
            compressed_size = dst_size;
            break;
#else
                 return -1;
#endif
        }
        default:
            return KPHC_COMPRESS_UNSUPPORTED_ERROR;
    }

    if ((compressed_size < 0) || ((offsetof(KPHCPageCompressData, data) + compressed_size) >= BLCKSZ)) {
        return -1;
    }

    pcdptr = ((KPHCPageCompressData*)dst);
    memcpy(pcdptr->page_header, src, sizeOfHeaderData);
    pcdptr->size = compressed_size;
    pcdptr->byte_convert = real_ByteConvert;
    pcdptr->diff_convert = option.diffConvert;
    pcdptr->algorithm = option.compressAlgorithm;

    return offsetof(KPHCPageCompressData, data) + compressed_size;
}

char *KPHCCfsCompressPage(const char *buffer, KPHCRelFileCompressOption *option, uint8 *nchunks)
{
    uint8 algorithm = option->compressAlgorithm;
    uint32 chunkSize = CHUNK_SIZE_LIST[option->compressChunkSize];
    int32 workBufferSize = CompressPageBufferBound(buffer, algorithm);
    int8 level = option->compressLevelSymbol ? option->compressLevel : -option->compressLevel;
    uint8 preallocChunk = option->compressPreallocChunks;
    char *workBuffer;
    int compressBufferSize;
    uint32 bufferSize;
    if (workBufferSize < 0) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("mdwrite algorithm:%d, chunkSize:%d, level:%d, preallocChunk:%d",
                    (int)algorithm, (int)chunkSize, level, (int)preallocChunk)));
    }

    workBuffer = (char*)palloc((unsigned long)workBufferSize);
    compressBufferSize = KPHCCompressPage(buffer, workBuffer, workBufferSize, *option);
    if (compressBufferSize < 0) {
        compressBufferSize = BLCKSZ;
    }
    *nchunks = (uint8)((unsigned int)(compressBufferSize - 1) / chunkSize + 1);
    bufferSize = chunkSize * (*nchunks);
    if (bufferSize >= BLCKSZ) {
        pfree(workBuffer);
        workBuffer = (char*)buffer;
        (*nchunks) = (uint8)(BLCKSZ / chunkSize);
    } else {
        if ((uint32)compressBufferSize < bufferSize) {
            uint32 leftSize = bufferSize - (uint32)compressBufferSize;
            memset(workBuffer + compressBufferSize, 0, leftSize);
        }
    }

    return workBuffer;
}

int KPHCDecompressPage(const char* src, char* dst)
{
    int decompressed_size;
    char* data;
    int ret;
    int desLength;
#ifdef USE_ZSTD
    uint32 size;
#endif
    int algorithm;
    size_t headerSize = SizeOfPageHeaderData;
    memcpy(dst, src, headerSize);
    data = ((KPHCPageCompressData*)src)->data;
#ifdef USE_ZSTD
    size = ((KPHCPageCompressData*)src)->size;
#endif
    algorithm = ((KPHCPageCompressData*)src)->algorithm;
    
    switch (algorithm) {
        case KPHC_COMPRESS_ALGORITHM_PGLZ:
            decompressed_size = KPHCLzDecompress((const KPHC_PGLZ_Header *)data, dst + headerSize);
            if (decompressed_size == -1) {
                return -1;
            }
            break;
        case KPHC_COMPRESS_ALGORITHM_ZSTD:
#ifdef USE_ZSTD
            decompressed_size = ZSTD_decompress(dst + headerSize, BLCKSZ - headerSize, data, size);
            if (ZSTD_isError(decompressed_size)) {
                return -1;
            }
#else
            return -1;
#endif
            break;
        case KPHC_COMPRESS_ALGORITHM_ZLIB:
#ifdef HAVE_LIBZ
            desLength = BLCKSZ - headerSize;
            ret = uncompress((Bytef *)(dst + headerSize), (uLongf *)&desLength, data, ((KPHCPageCompressData*)src)->size);
            if (ret != Z_OK) {
                return -1;
            }
            decompressed_size = desLength;
            break;
#else
            return -1;
#endif
        default:
            return KPHC_COMPRESS_UNSUPPORTED_ERROR;
            break;
    }
    return headerSize + decompressed_size;
}

void KPHCAnalyzeCompressOptions(const RelFileNode node, KPHCRelFileCompressOption* opt)
{
    unsigned short compressOption = node.opt;
    opt->compressChunkSize = compressOption & gCmpBitStruct[KPHC_CMP_CHUNK_SIZE_INDEX].mask;
    compressOption = compressOption >> gCmpBitStruct[KPHC_CMP_CHUNK_SIZE_INDEX].bitLen;
    opt->compressAlgorithm = compressOption & gCmpBitStruct[KPHC_CMP_ALGORITHM_INDEX].mask;
    compressOption = compressOption >> gCmpBitStruct[KPHC_CMP_ALGORITHM_INDEX].bitLen;
    opt->compressLevel = compressOption & gCmpBitStruct[KPHC_CMP_LEVEL_INDEX].mask;
    compressOption = compressOption >> gCmpBitStruct[KPHC_CMP_LEVEL_INDEX].bitLen;
    opt->compressLevelSymbol = compressOption & gCmpBitStruct[KPHC_CMP_COMPERSS_LEVEL_SYMBOL_INDEX].mask;
    compressOption = compressOption >> gCmpBitStruct[KPHC_CMP_COMPERSS_LEVEL_SYMBOL_INDEX].bitLen;
    opt->compressPreallocChunks = compressOption & gCmpBitStruct[KPHC_CMP_PRE_CHUNK_INDEX].mask;
    compressOption = compressOption >> gCmpBitStruct[KPHC_CMP_PRE_CHUNK_INDEX].bitLen;
    opt->diffConvert = compressOption & gCmpBitStruct[KPHC_CMP_DIFF_CONVERT_INDEX].mask;
    compressOption = compressOption >> gCmpBitStruct[KPHC_CMP_DIFF_CONVERT_INDEX].bitLen;
    opt->byteConvert = compressOption & gCmpBitStruct[KPHC_CMP_BYTE_CONVERT_INDEX].mask;
    compressOption = compressOption >> gCmpBitStruct[KPHC_CMP_BYTE_CONVERT_INDEX].bitLen;
}

size_t KPHCCfsWritePage(SMgrRelation reln, ForkNumber forknum, BlockNumber logicBlockNumber, char *buffer, bool sipSync, KPHC_CFS_STORAGE_TYPE type)
{
    uint16 chunkSize;
    uint8 start;
    int writeAmount;
    uint8 nchunks;
    char *compressedBuffer;
    uint8 needChunks;
    bool changed;
    off_t extentStartOffset;
    int nbytes;
    KPHCExtentLocation location = cfsLocationConverts[type](reln, forknum, logicBlockNumber, sipSync, KPHC_EXTENT_OPEN_FILE);
    KPHCPciPageCtrl *ctrl = KPHCPciBufReadPage(location, LW_SHARED, PCI_BUF_NORMAL_READ);
    KPHCCfsExtentHeader *cfsExtentHeader = ctrl->pciPage;
    KPHCCfsExtentAddress *cfsExtentAddress = KPHCGetExtentAddress(cfsExtentHeader, (uint16)location.extentOffset);
    
    // 获取压缩参数
    KPHCRelFileCompressOption option;
    KPHCAnalyzeCompressOptions(reln->smgr_rnode.node, &option);
    chunkSize = cfsExtentHeader->chunk_size;

    // 执行页压缩
    compressedBuffer = KPHCCfsCompressPage(buffer, &option, &nchunks);

    // 设置地址
    needChunks = option.compressPreallocChunks > nchunks ? (uint8)option.compressPreallocChunks : (uint8)nchunks;
    changed = ExtendChunksOfBlock(cfsExtentHeader, &location, needChunks, nchunks);

    extentStartOffset = location.extentStart * BLCKSZ;

    // 写入每个chunk
    for (size_t i = 0; i < nchunks; ++i) {
        size_t bufferPos = (size_t)compressedBuffer + (long)chunkSize * i;
        off_t seekPos = OffsetOfPageCompressChunk(chunkSize, cfsExtentAddress->chunknos[i]);
        if (cfsExtentAddress->chunknos[i] > ((BLCKSZ / chunkSize) * KPHC_CFS_LOGIC_BLOCKS_PER_EXTENT)) {
            *((uint32 *)NULL) = 1; // ???
        }
        start = i;
        while (i < nchunks - 1 && cfsExtentAddress->chunknos[i + 1] == cfsExtentAddress->chunknos[i] + 1) {
            i++;
        }
        writeAmount = chunkSize * ((i - start) + 1);
        if ((seekPos + extentStartOffset) > (((BlockNumber)RELSEG_SIZE) * BLCKSZ)) {
            *((uint32 *)NULL) = 1; // ???
        }
        nbytes = FileWrite(location.fd, (char *)bufferPos, writeAmount, seekPos + extentStartOffset, (uint32)WAIT_EVENT_DATA_FILE_WRITE);
        if (nbytes != writeAmount) {
            if (compressedBuffer != NULL && compressedBuffer != buffer) {
                pfree(compressedBuffer);
            }
            KPHCPciBufFreePage(ctrl, location, false);
        }
    }

    if (compressedBuffer != NULL && compressedBuffer != buffer) {
        pfree(compressedBuffer);
    }
    KPHCPciBufFreePage(ctrl, location, changed);
    return BLCKSZ;
}

int KPHCCfsReadPage(SMgrRelation reln, ForkNumber forknum, BlockNumber logicBlockNumber, char *buffer, KPHC_CFS_STORAGE_TYPE type)
{
	uint32 chunkSize;
	uint32 startOffset;
	char *compressedBuffer;
	char *bufferPos;
	off_t seekPos;
	uint8 start;
	int readAmount;
    KPHCExtentLocation location = cfsLocationConverts[type](reln, forknum, logicBlockNumber, false, KPHC_EXTENT_OPEN_FILE);
    KPHCPciPageCtrl *ctrl = KPHCPciBufReadPage(location, LW_SHARED, PCI_BUF_NORMAL_READ);
    KPHCCfsExtentHeader *cfsExtentHeader = ctrl->pciPage;
    KPHCCfsExtentAddress *cfsExtentAddress = KPHCGetExtentAddress(cfsExtentHeader, (uint16)location.extentOffset);
    KPHCRelFileCompressOption option;
    KPHCAnalyzeCompressOptions(reln->smgr_rnode.node, &option);
    chunkSize = CHUNK_SIZE_LIST[option.compressChunkSize];
    startOffset = location.extentStart * BLCKSZ;
    compressedBuffer = (char *) palloc(chunkSize * cfsExtentAddress->nchunks);
    bufferPos = compressedBuffer;

    for (uint32 i = 0; i < cfsExtentAddress->nchunks; i++) {
        bufferPos = compressedBuffer + (long)chunkSize * i;
        seekPos = OffsetOfPageCompressChunk((uint16)chunkSize, cfsExtentAddress->chunknos[i]) + startOffset;
        start = (uint8)i;
        while (i < cfsExtentAddress->nchunks - 1 &&
               cfsExtentAddress->chunknos[i + 1] == cfsExtentAddress->chunknos[i] + 1) {
            i++;
        }
        readAmount = (int)(chunkSize * ((int)(i - (int)start) + 1));
        FileRead(location.fd, bufferPos, readAmount, seekPos, (uint32)WAIT_EVENT_DATA_FILE_READ);
    }

    if (cfsExtentAddress->nchunks == (BLCKSZ / chunkSize)) {
        memcpy(buffer, compressedBuffer, BLCKSZ);
    } else if (KPHCDecompressPage(compressedBuffer, buffer) != BLCKSZ) {
        memset(buffer, 0, BLCKSZ);
        KPHCPciBufFreePage(ctrl, location, false);
        pfree(compressedBuffer);
        return -1;
    }
    KPHCPciBufFreePage(ctrl, location, false);
    pfree(compressedBuffer);
    return BLCKSZ;
}

off_t KPHCCfsMdTruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber logicBlockNumber, bool skipSync, KPHC_CFS_STORAGE_TYPE type)
{
    KPHCExtentLocation location = cfsLocationConverts[type](reln, forknum, logicBlockNumber, skipSync, KPHC_EXTENT_OPEN_FILE);
    /* if logicBlockNumber is the first block of extent, truncate all after blocks */
    if (logicBlockNumber % KPHC_CFS_LOGIC_BLOCKS_PER_EXTENT == 0) {
        return location.extentStart * BLCKSZ;
    }

    off_t truncateOffset = (location.headerNum + 1) * BLCKSZ;
    KPHCPciPageCtrl *ctrl = KPHCPciBufReadPage(location, LW_SHARED, PCI_BUF_NORMAL_READ);
    if (ctrl->loadStatus == CTRL_PAGE_LOADED_ERROR) {
        KPHCPciBufFreePage(ctrl, location, false);
        ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED),
            errmsg("Failed to CfsMdTruncate %s, headerNum: %u.", FilePathName(location.fd), location.headerNum)));
    }
    KPHCCfsExtentHeader *cfsExtentHeader = ctrl->pciPage;

    for (int i = (int)location.extentOffset; i < KPHC_CFS_LOGIC_BLOCKS_PER_EXTENT; i++) {
        KPHCCfsExtentAddress *cfsExtentAddress = KPHCGetExtentAddress(cfsExtentHeader, (uint16)i);

        cfsExtentAddress->nchunks = 0;
        cfsExtentAddress->checksum = AddrChecksum32(cfsExtentAddress, cfsExtentAddress->allocated_chunks);
    }

    uint16 max = 0;
    for (uint16 i = 0; i < location.extentOffset; i++) {
        KPHCCfsExtentAddress *cfsExtentAddress = KPHCGetExtentAddress(cfsExtentHeader, i);
        for (int j = 0; j < cfsExtentAddress->allocated_chunks; j++) {
            max = (max > cfsExtentAddress->chunknos[j]) ? max : cfsExtentAddress->chunknos[j];
        }
    }

    pg_atomic_write_u32(&cfsExtentHeader->nblocks, location.extentOffset);

    /* need sync cfs header */
    KPHCPciBufFreePage(ctrl, location, true);

    /* File allocate (file hole) */
    uint32 start = location.extentStart * BLCKSZ + max * cfsExtentHeader->chunk_size;
    uint32 len = (uint32)((KPHC_CFS_MAX_LOGIC_CHUNKS_NUMBER(cfsExtentHeader->chunk_size) - max) *
                          cfsExtentHeader->chunk_size);
    if (len >= MIN_FALLOCATE_SIZE) {
        start += (len % MIN_FALLOCATE_SIZE);
        len -= (len % MIN_FALLOCATE_SIZE);
        KPHCFileAllocate(location.fd, start, len);
    }
    return truncateOffset;
}
